﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace Sinacor.Infra.Common.Security.Cryptography
{
    public class DsaSigner
    {
        private const string CONTAINER_NAME = "{A50CE5CD-DEE9-4636-A5E4-50E0AF448C1F}";

        public static SignedData Sign(string message, int keySize)
        {
            KeyPair keyPair;
            SignedData result;

            keyPair = CreateKeys(keySize);
            result = SignDsaMessage(message, keyPair);
            
            return result;
        }

        public static bool VerifySignature(byte[] message, byte[] publickey, byte[] signature)
        {
            bool result = false;

            // Cannot use PROV_DSS_DH
            const int PROV_DSS = 3;
            const int AT_SIGNATURE = 2;

            //
            // Load the Public Key
            //   X.509 Format
            //
            AsnKeyParser keyParser = new AsnKeyParser(publickey);

            DSAParameters publicKey = keyParser.ParseDSAPublicKey();

            //
            // Initailize the CSP
            //
            CspParameters csp = new CspParameters();
            csp.ProviderType = PROV_DSS;
            csp.KeyNumber = AT_SIGNATURE;
            csp.KeyContainerName = CONTAINER_NAME;

            //
            // Initialize the Provider
            //
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(csp);
            dsa.PersistKeyInCsp = false;

            //
            // The moment of truth...
            //
            dsa.ImportParameters(publicKey);

            //
            // Compute h(m)
            //
            SHA1 sha = new SHA1CryptoServiceProvider();
            byte[] hash = sha.ComputeHash(message);

            //
            // Initialize Verifier
            //
            DSASignatureDeformatter verifier = new DSASignatureDeformatter(dsa);
            verifier.SetHashAlgorithm("SHA1");

            if (verifier.VerifySignature(hash, signature))
            {
                UTF8Encoding utf8 = new UTF8Encoding();
                String s = utf8.GetString(message);

                result = true;
            }
            else
            {
                result = false;
            }

            // See http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
            dsa.Clear();

            return result;
        }

        private static SignedData SignDsaMessage(string expToSign, KeyPair keyPair)
        {
            SignedData result = new SignedData();
            // Cannot use PROV_DSS_DH
            const int PROV_DSS = 3;
            const int AT_SIGNATURE = 2;

            //
            // Load the Private Key
            //   PKCS#8 Format
            //
            AsnKeyParser keyParser = new AsnKeyParser(keyPair.PrivateKey.GetBytes());

            DSAParameters privateKey = keyParser.ParseDSAPrivateKey();

            //
            // Initailize the CSP
            //   Supresses creation of a new key
            //
            CspParameters csp = new CspParameters();
            csp.KeyContainerName = CONTAINER_NAME;
            csp.ProviderType = PROV_DSS;
            csp.KeyNumber = AT_SIGNATURE;

            //
            // Initialize the Provider
            //
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(csp);
            dsa.PersistKeyInCsp = false;

            //
            // The moment of truth...
            //
            dsa.ImportParameters(privateKey);

            //
            // Sign the Message
            //
            DSASignatureFormatter signer = new DSASignatureFormatter(dsa);
            signer.SetHashAlgorithm("SHA1");

            // The one and only
            byte[] message = Encoding.GetEncoding("UTF-8").GetBytes(expToSign);

            // h(m)
            SHA1 sha = new SHA1CryptoServiceProvider();
            byte[] hash = sha.ComputeHash(message);

            // Create the Signature for h(m)
            byte[] signature = signer.CreateSignature(hash);

            // Write the message
            result.Key = keyPair.PublicKey.GetBytes();

            // Write the signature on the message
            result.Signature = signature;

            dsa.Clear();

            return result;
        }

        private static KeyPair CreateKeys(int keySize)
        {
            const int PROV_DSS_DH = 13;
            const int AT_SIGNATURE = 2;

            KeyPair result = new KeyPair();

            CspParameters csp = new CspParameters();
            csp.KeyContainerName = CONTAINER_NAME;
            csp.ProviderType = PROV_DSS_DH;
            csp.KeyNumber = AT_SIGNATURE;

            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(keySize, csp);
            dsa.PersistKeyInCsp = false;

            // Create a private key
            DSAParameters privateKey = dsa.ExportParameters(true);
            result.PrivateKey = AsnKeyBuilder.PrivateKeyToPKCS8(privateKey);

            // Create a public key
            DSAParameters publicKey = dsa.ExportParameters(false);
            result.PublicKey = AsnKeyBuilder.PublicKeyToX509(publicKey);

            // See http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
            dsa.Clear();

            return result;
        }

        internal class KeyPair
        {
            private AsnKeyBuilder.AsnMessage _privateKey;
            public AsnKeyBuilder.AsnMessage PrivateKey
            {
                get { return _privateKey; }
                set { _privateKey = value; }
            }

            private AsnKeyBuilder.AsnMessage _publicKey;
            public AsnKeyBuilder.AsnMessage PublicKey
            {
                get { return _publicKey; }
                set { _publicKey = value; }
            }
        }

        public class SignedData
        {
            private byte[] _key;
            public byte[] Key
            {
                get { return _key; }
                set { _key = value; }
            }

            private byte[] _signature;
            public byte[] Signature
            {
                get { return _signature; }
                set { _signature = value; }
            }

        }
    }
}
